<script setup lang="ts">
import type {
  AppPosition,
  CommandProps,
  KeyValueProps,
} from '@/types';

import {
  type ComponentInternalInstance,
  computed,
  defineAsyncComponent,
  getCurrentInstance,
  hydrateOnVisible,
  onMounted,
  onUnmounted,
  type PropType,
  reactive,
  ref,
  useTemplateRef,
  watch,
} from 'vue';

import { Edit } from '@element-plus/icons-vue';
import {
  dayjs,
  ElButton,
  ElButtonGroup,
  ElCheckbox,
  ElIcon,
  ElInput,
  ElMessage,
  ElMessageBox,
} from 'element-plus';
import { cloneDeep } from 'lodash-es';
import bus from 'vue3-eventbus';

import { Horizontal } from '@/components/core/resize-layout';
import { useWebSocketStore } from '@/store/websocket';
import { removeNonUtf8Chars } from '@/utils';

defineOptions({
  name: 'WorkDebug',
});
const { nowRect } = defineProps({
  nowRect: {
    default: () => ({}),
    type: Object as PropType<AppPosition>,
  },
});
const wsStore = useWebSocketStore();
const Toolbar = defineAsyncComponent({
  hydrate: hydrateOnVisible(),
  loader: () => import('./components/debug/Toolbar.vue'),
});
const Taskbar = defineAsyncComponent({
  hydrate: hydrateOnVisible(),
  loader: () => import('./components/debug/Taskbar.vue'),
});
const CommandAction = defineAsyncComponent({
  hydrate: hydrateOnVisible(),
  loader: () => import('./components/debug/CommandAction.vue'),
});
const CommandBox = defineAsyncComponent({
  hydrate: hydrateOnVisible(),
  loader: () => import('./components/debug/CommandBox.vue'),
});
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const baseUrl = proxy?.$config.baseUrl;

const consoleElRef = useTemplateRef('consoleElRef');

const layoutSize = reactive({
  lw: 0,
  mw: 0,
});
const contentRightStyle = computed(() => {
  if (layoutSize.mw <= 480) {
    return {
      height: `calc(100% - 64px)`,
    };
  }
  return {
    height: `calc(100% - 32px)`,
  };
});

const defaultCommands = Array.from({ length: 30 }).map(
  (_, i) =>
    ({
      checked: false,
      command: '',
      id: i,
      remark: '',
    }) as CommandProps,
) as CommandProps[];
const commands = ref<Array<CommandProps>>(defaultCommands);
// 水平分隔左右宽度
const onHorizontalResize = (data: { lw: number; mw: number }) => {
  Object.assign(layoutSize, data);
};
const taskExecing = ref(false); // 组合指令执行中
const taskExecMsg = ref(''); // 组合指令执行中
const stopExecTask = ref(false); // 停止组合指令

const socketState = ref(0); // 0:未连接 1:已连接 2:断开
const socketUrl = ref(''); // 连接地址
const maxReconnectAttempts = ref(false);
const EventStore: KeyValueProps = {};
const setupWebSocket = (url: string) => {
  if (maxReconnectAttempts.value) {
    wsStore.resetState();
  }
  wsStore.connect(url);
};
const consoleHTML = (type: string, msg: string) => {
  const container = consoleElRef.value as HTMLElement;
  if (msg) {
    const ele = document.createElement('div');
    ele.className = `${type}`;
    ele.innerText = `${msg}`;
    container.append(ele);
    // 检查子元素数量，如果超过100条则删除最早的子元素
    while (container.children?.length > 500) {
      container.firstChild!.remove();
    }
  }
  container.scrollTo(0, container.scrollHeight);
};
const onWsOpen = (data: { message: string; readyState: number }) => {
  const key = 'ws-open';
  if (EventStore[key]) return;
  EventStore[key] = true;
  socketState.value = data.readyState;
  if (data.readyState === 0) {
    consoleHTML('warning', `正在连接 ${socketUrl.value}...`);
  } else if (data.readyState === 1) {
    consoleHTML('success', `连接 ${socketUrl.value} 成功`);
  }

  delete EventStore[key];
  bus.off(key);
};
const onWsDisconnect = (data: { message: string; readyState: number }) => {
  const key = 'ws-disconnect';
  if (EventStore[key]) return;
  EventStore[key] = true;
  socketState.value = data.readyState;
  consoleHTML('info', `连接正在断开...`);

  delete EventStore[key];
  bus.off(key);
};
const onWsClose = (data: { message: string; readyState: number }) => {
  const key = 'ws-close';
  if (EventStore[key]) return;
  EventStore[key] = true;
  socketState.value = data.readyState;
  consoleHTML('danger', `连接已关闭`);

  delete EventStore[key];
  bus.off(key);
};

const onWsMessage = (data: string) => {
  const key = 'ws-message';
  if (EventStore[key]) return;
  EventStore[key] = true;
  try {
    const json = typeof data === 'string' ? JSON.parse(data) : data;
    if (json.method === 'HeartBeat') {
      consoleHTML('info', data);
    } else {
      consoleHTML('default', `${data}`);
    }
  } catch (error) {
    consoleHTML('default', `发生错误：${error}`);
  }

  delete EventStore[key];
  bus.off(key);
};
const onWsReconnect = (data: {
  maxReconnectAttempts: boolean;
  message: string;
}) => {
  const key = 'ws-reconnect';
  if (EventStore[key]) return;
  EventStore[key] = true;
  consoleHTML('warning', `${data.message}`);
  maxReconnectAttempts.value = data.maxReconnectAttempts;
  delete EventStore[key];
  bus.off(key);
};

// 发送单指令
function sendCommand(command: string) {
  if (taskExecing.value) {
    ElMessage.warning('组合指令执行中，请先停止');
    return;
  }
  if (!command) return;
  const fcReg = /\w+\s*\(.*?\)/; //根据实际指令格式校验

  if (!fcReg.test(command)) {
    consoleHTML('danger', `无效指令： ${command}`);
    return;
  }
  consoleHTML('primary', `${dayjs().format('YYYY-MM-DD HH:mm:ss')} ${command}`);
  wsStore.send(removeNonUtf8Chars(command));
  ElMessage.success({ grouping: true, message: '指令已发送' });
}
// 执行
const onCommandExec = (item: CommandProps) => {
  sendCommand(item.command);
};
const onCommandKeyDown = (e: KeyboardEvent, item: CommandProps) => {
  if (e.key === 'Enter') {
    sendCommand(item.command);
  }
};

// 备注
const onCommandRemark = (command: CommandProps) => {
  ElMessageBox.prompt('请输入指令备注', '指令备注', {
    buttonSize: 'small',
    cancelButtonText: '取消',
    confirmButtonText: '确定',
    customClass: 'wos-common-confirm',
    draggable: true,
    inputErrorMessage: '无效的备注，请重新输入',
    inputPattern: /^$|^[\u4E00-\u9FA5a-z0-9]+$/i,
    inputPlaceholder: '请输入指令备注',
    inputValue: command.remark,
  })
    .then(({ value }) => {
      Object.assign(command, { remark: value });
    })
    .catch(() => {
      // console.log('取消');
    });
};
// 清屏
const onCleanup = () => {
  if (consoleElRef.value) consoleElRef.value.innerHTML = '';
  ElMessage.success('已清理');
};
// 连接
const onConnect = (url: string) => {
  if (!url) return;
  setupWebSocket(url);
};
// 连接
const onDisconnect = () => {
  // console.log('断开', wsStore.ws);
  wsStore.disconnect();
};
// 单指令
const onSendCommand = (command: string) => {
  sendCommand(command);
};
// 停止组合指令测试
const onStopTaskCommand = () => {
  ElMessageBox.confirm('确定停止正在执行指令？', '提示', {
    buttonSize: 'small',
    cancelButtonText: '取消',
    confirmButtonText: '确定',
    customClass: 'wos-common-confirm',
    type: 'warning',
  })
    .then(() => {
      stopExecTask.value = true;
      taskExecing.value = false;
      taskExecMsg.value = `已停止`;
      ElMessage.success('指令已停止');
    })
    .catch(() => {});
};

// 组合指令测试
const onSendTaskCommand = (data: any) => {
  const list = commands.value?.filter((item) => item.checked);
  if (!list?.length) {
    ElMessage.warning('请选择要执行的指令');
    return;
  }
  ElMessageBox.confirm('确定执行选中的指令？', '提示', {
    buttonSize: 'small',
    cancelButtonText: '取消',
    confirmButtonText: '确定',
    customClass: 'wos-common-confirm',
    type: 'warning',
  })
    .then(async () => {
      ElMessage.success('指令集已发送');
      taskExecMsg.value = ``;
      let hasStop = false;

      // eslint-disable-next-line no-restricted-syntax, no-labels
      loop: for (let i = 0; i < data.loop; i++) {
        taskExecing.value = true;
        for (const element of list) {
          hasStop = stopExecTask.value;

          // eslint-disable-next-line no-labels
          if (stopExecTask.value) break loop;
          await new Promise((resolve) => {
            const command = element.command;
            sendCommand(command);

            setTimeout(() => {
              resolve(true);
            }, data.delay);
          });
        }
        taskExecMsg.value = `已执行第${i + 1}次`;
      }
      stopExecTask.value = false;
      taskExecing.value = false;
      if (!hasStop) taskExecMsg.value = `执行完成`;
    })
    .catch(() => {});
};

// 新增新指令
const onAddCommand = () => {
  const id = commands.value?.length || 0;
  commands.value?.push({
    checked: false,
    command: '',
    id,
    remark: '',
  });
};
// 删除新指令
const onDelCommand = () => {
  if (!commands.value?.filter((item) => item.checked)?.length) {
    ElMessage.warning('请选择要删除的指令');
    return;
  }
  ElMessageBox.confirm('确定删除选中的指令？', '提示', {
    buttonSize: 'small',
    cancelButtonText: '取消',
    confirmButtonText: '确定',
    customClass: 'wos-common-confirm',
    type: 'warning',
  })
    .then(() => {
      commands.value = commands.value?.filter((item) => !item.checked);
    })
    .catch(() => {});
};
// 保存指令
const onSaveCommand = (file = 'commands') => {
  try {
    let bodyData = commands.value || [];
    // 如果是指令集，使用方法名作为主键
    if (file !== 'commands') {
      bodyData = cloneDeep(bodyData).map((item:CommandProps) => {
        if (item?.command && /^\s*[^\s(]+/.test(item.command)) {
          item.command = item.command.match(/^\s*([^\s(]+)/)![1] as string;
        }
        return item;
      });
    }
    const body = {
      commands: bodyData,
    };
    const url = `${baseUrl}/debug/save/${file}`;
    fetch(url, {
      body: JSON.stringify(body),
      headers: {
        'Content-Type': 'application/json;charset=utf-8',
      },
      method: 'POST',
    })
      .then((response) => response.json())
      .then((res) => {
        const { code, msg } = res;
        if (code === 200) {
          ElMessage.success(msg);
        } else {
          ElMessage.error(msg);
        }
      });
  } catch {
    // console.log(error);
    ElMessage.error('保存失败，请稍后重试');
  }
};
const onCommand = (data: { action: string; param: string }) => {
  switch (data.action) {
    case 'add': {
      onAddCommand();
      break;
    }
    case 'cleanup': {
      onCleanup();
      break;
    }
    case 'connect': {
      onConnect(data.param);
      break;
    }
    case 'del': {
      onDelCommand();
      break;
    }
    case 'disconnect': {
      onDisconnect();
      break;
    }
    case 'save': {
      onSaveCommand(data.param);
      break;
    }
    case 'send-command': {
      onSendCommand(data.param);
      break;
    }
    case 'send-task': {
      onSendTaskCommand(data.param);
      break;
    }
    case 'stop-task': {
      onStopTaskCommand();
      break;
    }
  }
};
const loadCommands = async () => {
  try {
    const url = `${baseUrl}/debug/commands/commands`;
    const request = await fetch(url, {
      headers: {
        'Content-Type': 'application/json;charset=utf-8',
      },
      method: 'GET',
    });

    const res = await request.json();
    const { code, data, msg } = res;
    if (code === 200 && data) {
      commands.value = typeof data === 'string' ? JSON.parse(data) : data;
    } else {
      ElMessage.error(msg);
    }
  } catch {
    // console.log(error);
  }
};

onMounted(async () => {
  bus.on('ws-open', onWsOpen);
  bus.on('ws-close', onWsClose);
  bus.on('ws-disconnect', onWsDisconnect);
  bus.on('ws-message', onWsMessage);
  bus.on('ws-reconnect', onWsReconnect);
  await loadCommands();
});

onUnmounted(() => {
  bus.off('ws-open', onWsOpen);
  bus.off('ws-close', onWsClose);
  bus.off('ws-disconnect', onWsDisconnect);
  bus.off('ws-message', onWsMessage);
  bus.off('ws-reconnect', onWsReconnect);
});

watch(
  () => wsStore.url,
  (val) => {
    socketUrl.value = val;
    onConnect(val);
  },
  { deep: true, immediate: true },
);
</script>

<template>
  <div class="wos-debug">
    <Horizontal
      :default-width="420"
      :max-width="680"
      :min-width="400"
      :now-rect="nowRect"
      @resize="onHorizontalResize"
    >
      <template #aside>
        <div class="side-left">
          <div ref="consoleElRef" class="console">
            <div class="danger">* ----------------------------------------------</div>
            <div class="danger">* 目前只支持WebSocket通信；</div>
            <div class="danger">* 需要串口、TCP、UDP等功能请自行实现；</div>
            <div class="danger">* ----------------------------------------------</div>
          </div>
          <div class="connect">
            <!--控制台连接-->
            <CommandAction
              :socket-state="socketState"
              :url="socketUrl"
              @command="onCommand"
            />
            <!--控制台发送指令-->
            <CommandBox :socket-state="socketState" @command="onCommand" />
          </div>
        </div>
      </template>
      <template #main>
        <div :style="contentRightStyle" class="content-right">
          <!--任务参数-->
          <Taskbar
            :execing="taskExecing"
            :execmsg="taskExecMsg"
            :mw="layoutSize.mw"
            @command="onCommand"
          />
          <div class="content">
            <!--指令切换-->
            <Toolbar
              :mw="layoutSize.mw"
              @command="onCommand"
            />
            <div class="list">
              <table class="hor-minimalist-b">
                <colgroup>
                  <col width="28" />
                  <col width="auto" />
                  <col width="156" />
                </colgroup>
                <thead>
                  <tr>
                    <th scope="col"></th>
                    <th scope="col">指令</th>
                    <th scope="col">点击执行</th>
                  </tr>
                </thead>
                <tbody>
                  <template v-for="(item, index) in commands" :key="item.id">
                    <tr>
                      <td class="checkbox">
                        <ElCheckbox v-model="item.checked" size="small" />
                      </td>
                      <td>
                        <div class="command">
                          <label>{{ index + 1 }}.</label>
                          <ElInput
                            v-model="item.command"
                            placeholder="请输入单指令"
                            @keypress="
                              (evt: KeyboardEvent) =>
                                onCommandKeyDown(evt, item)
                            "
                          />
                        </div>
                      </td>
                      <td>
                        <div class="action">
                          <ElButtonGroup size="small">
                            <ElButton
                              :class="{
                                '!text-blue-500':
                                  item.remark && item.remark !== '执行',
                              }"
                              :title="item.remark"
                              class="btn-remark"
                              size="small"
                              @click="onCommandExec(item)"
                            >
                              {{ item.remark || '执行' }}
                            </ElButton>
                            <ElButton
                              size="small"
                              @click="onCommandRemark(item)"
                            >
                              <ElIcon
                                class="cursor-pointer"
                                size="16px"
                                title="添加备注"
                              >
                                <Edit />
                              </ElIcon>
                            </ElButton>
                          </ElButtonGroup>
                        </div>
                      </td>
                    </tr>
                  </template>
                </tbody>
              </table>
            </div>
          </div>
        </div>
      </template>
    </Horizontal>
  </div>
</template>

<style lang="scss">
@import url('./css/debug.scss');
</style>
